﻿class Navigator {
    constructor(workflowEditorEleId) {
        this.workflowEditorEle = document.getElementById(workflowEditorEleId);
        this.workflowViewportEle = this.workflowEditorEle.querySelector(".workflow-viewport");
        this.workflowCanvasEle = this.workflowEditorEle.querySelector('.workflow-viewport > .workflow-canvas');
        this.workflowNavigatorContainerEle = this.workflowEditorEle.querySelector('.workflow-navigator-container');
        this.workflowNavigatorEle = this.workflowNavigatorContainerEle.querySelector('.workflow-navigator');
        this.navigatorWorkflowViewportEle = this.workflowNavigatorEle.querySelector('.navigator-workflow-viewport');
        this.navigatorWorkflowCanvasEle = this.navigatorWorkflowViewportEle.querySelector('.navigator-workflow-canvas');
        this.workflowNavigatorEle.addEventListener('mousewheel', (e) => this.middleMouseZoom(e));
        this.workflowNavigatorEle.onpointerdown = (e) => {
            this.workflowNavigatorEle.onpointermove = (e) => {
                if (e.buttons !== 1) {
                    this.workflowNavigatorEle.onpointermove = null;
                    this.workflowNavigatorEle.releasePointerCapture(e.pointerId);
                    return;
                }
                this.workflowEditorEle.workflow.pan(-e.movementX / this.viewScale, -e.movementY / this.viewScale);
                this.layout();
            };
            this.workflowNavigatorEle.style.cursor = 'grabbing';
            this.workflowNavigatorEle.setPointerCapture(e.pointerId);
        };
        this.workflowNavigatorEle.onpointerup = (e) => {
            this.workflowNavigatorEle.onpointermove = null;
            this.workflowNavigatorEle.releasePointerCapture(e.pointerId);
            this.workflowNavigatorEle.style.cursor = 'grab';
        };
        this.relayout();
    }

    layout() {
        const viewportSize = this.workflowEditorEle.workflow.getViewportSize();
        const canvasBounds = this.workflowEditorEle.workflow.getCanvasBounds();
        const canvasScale = this.workflowEditorEle.workflow.getCanvasScale();
        const viewportActualBounds = {
            left: Math.min(0, canvasBounds.left * canvasScale),
            top: Math.min(0, canvasBounds.top * canvasScale),
            right: Math.max(viewportSize.width, (canvasBounds.width + canvasBounds.left) * canvasScale),
            bottom: Math.max(viewportSize.height, (canvasBounds.height + canvasBounds.top) * canvasScale)
        };
        viewportActualBounds.width = viewportActualBounds.right - viewportActualBounds.left;
        viewportActualBounds.height = viewportActualBounds.bottom - viewportActualBounds.top;
        const navigatorSize = {
            width: this.workflowNavigatorEle.offsetWidth,
            height: this.workflowNavigatorEle.offsetHeight
        };
        const viewBounds = {};
        if (navigatorSize.width / navigatorSize.height > viewportActualBounds.width / viewportActualBounds.height) {
            viewBounds.height = navigatorSize.height;
            viewBounds.width = viewBounds.height * (viewportActualBounds.width / viewportActualBounds.height);
            viewBounds.left = (navigatorSize.width - viewBounds.width) / 2;
            viewBounds.top = 0;
        } else {
            viewBounds.width = navigatorSize.width;
            viewBounds.height = viewBounds.width / (viewportActualBounds.width / viewportActualBounds.height);
            viewBounds.left = 0;
            viewBounds.top = (navigatorSize.height - viewBounds.height) / 2;
        }
        this.viewScale = viewBounds.width / viewportActualBounds.width;
        this.navigatorWorkflowViewportEle.style.left = `${viewBounds.left}px`;
        this.navigatorWorkflowViewportEle.style.top = `${viewBounds.top}px`;
        this.navigatorWorkflowViewportEle.style.width = `${viewportSize.width}px`;
        this.navigatorWorkflowViewportEle.style.height = `${viewportSize.height}px`;
        this.navigatorWorkflowViewportEle.style.transform = `scale(${this.viewScale}) translate(${-viewportActualBounds.left}px, ${-viewportActualBounds.top}px)`;
        this.navigatorWorkflowCanvasEle.style.transform = this.workflowCanvasEle.style.transform;
    }

    relayout() {
        let relayout = false;
        let sizeChanged = false;
        if (parseInt(this.workflowEditorEle.offsetWidth) !== this.workflowEditorEleWidth) {
            this.workflowEditorEleWidth = parseInt(this.workflowEditorEle.offsetWidth);
            relayout = true;
            sizeChanged = true;
        }
        if (parseInt(this.workflowEditorEle.offsetHeight) !== this.workflowEditorEleHeight) {
            this.workflowEditorEleHeight = parseInt(this.workflowEditorEle.offsetHeight);
            relayout = true;
            sizeChanged = true;
        }
        if (parseInt(this.workflowCanvasEle.offsetWidth) !== this.workflowCanvasEleWidth) {
            this.workflowCanvasEleWidth = parseInt(this.workflowCanvasEle.offsetWidth);
            relayout = true;
            sizeChanged = true;
        }
        if (parseInt(this.workflowCanvasEle.offsetHeight) !== this.workflowCanvasEleHeight) {
            this.workflowCanvasEleHeight = parseInt(this.workflowCanvasEle.offsetHeight);
            relayout = true;
            sizeChanged = true;
        }
        if (this.workflowCanvasEle.style.transform !== this.workflowCanvasEleTransform) {
            this.workflowCanvasEleTransform = this.workflowCanvasEle.style.transform;
            relayout = true;
        }
        if (relayout) {
            if (sizeChanged) {
                const nodeEles = this.workflowCanvasEle.querySelectorAll('.node-outline');
                const navigatorNodeEles = this.navigatorWorkflowCanvasEle.querySelectorAll('.node-outline');
                for (const nodeEle of nodeEles) {
                    let navigatorNodeEle;
                    for (navigatorNodeEle of navigatorNodeEles) {
                        if (navigatorNodeEle.getAttribute('outline-id') === nodeEle.getAttribute('outline-id')) {
                            break;
                        }
                    }
                    if (navigatorNodeEle) {
                        navigatorNodeEle.style.setProperty('--wf-node-outline-width', `${nodeEle.offsetWidth}px`);
                        navigatorNodeEle.style.setProperty('--wf-node-outline-height', `${nodeEle.offsetHeight}px`);
                    }
                }
            }
            this.layout();
        }
        if (!this.isDispose) {
            this.animationFrameHandle = window.requestAnimationFrame(() => this.relayout());
        }
    }

    middleMouseZoom(e) {
        let scale = this.workflowEditorEle.workflow.getCanvasScale();
        if (e.wheelDeltaY > 0) {
            scale += 0.1;
        } else {
            scale -= 0.1;
        }
        scale = Math.max(0.1, scale);
        scale = Math.min(1.5, scale);
        this.workflowEditorEle.workflow.zoomTo(scale);
        this.layout();
        e.preventDefault();
        e.stopPropagation();
    }

    dispose() {
        window.cancelAnimationFrame(this.animationFrameHandle);
        this.workflowNavigatorEle.onpointerdown = null;
        this.workflowNavigatorEle.onpointermove = null;
        this.workflowNavigatorEle.onpointerup = null;
        this.workflowNavigatorEle.removeEventListener('mousewheel', this.middleMouseZoomHandle);
        this.isDispose = true;
    }
}

export function init(workflowEleId) {
    const ele = document.getElementById(workflowEleId);
    if (ele) {
        ele.navigator = new Navigator(workflowEleId);
    }
}

export function dispose(workflowEleId) {
    const ele = document.getElementById(workflowEleId);
    if (ele?.navigator) {
        ele.navigator.dispose();
    }
}